InVesalius Plugins

Thiago Franco de Moraes
Paulo Henrique Junqueira Amorim

09/03/2022

InVesalius Plugins

Minimun required

Location

  • invesalius3/plugins
  • $HOME/.config/invesalius/plugins

One folder for each plugin

Pasta de plugins

Needed files

my_plugin/
├── __init__.py
├── main.py
└── plugin.json

__init__.py

  • Each plugin is a Python module
  • Use Python
    • But you can use other languages additionally
  • Use WXPython for graphical interface

plugins.json

{  
    "name": "Plugin name",  
    "description": "A little description",  
    "enable-startup": false 
}

enable-startup indicates whether the plugin can be used without an image loaded

main.py

Responsável por iniciar o plugin

import wx
from pubsub import pub as Publisher

import invesalius.data.slice_ as slc
from invesalius import project


def load():
    print("loading plugin")

Available Python libraries

❯ cat requirements.txt 
Cython==0.29.28
Pillow==9.3.0
Pypubsub==4.0.3
configparser==5.0.1
h5py==2.10.0
imageio==2.9.0
nibabel==3.2.1
numpy==1.22.1
plaidml-keras==0.7.0
psutil==5.8.0
pyserial==3.5
python-gdcm==3.0.12
scikit-image==0.19.1
scipy==1.7.3
vtk==9.1.0
wxPython==4.1.1
Theano==1.0.5
torch==1.13.1
pyacvd==0.2.7

InVesalius data access

project

  • Access to masks, surfaces, measures.
  • File invesalius/project.py
from invesalius import project
prj = project.Project()
prj.mask_dict
prj.surface_dict
prj.measurement_dict

slice

  • File invesalius/data/slice_.py
from invesalius.data.slice_ import Slice

slc = Slice()
image = slc.matrix # numpy array
first_pixel = image[0, 0, 0]
current_mask = slc.current_mask

mask

  • File invesalius/data/mask.py
from invesalius.data.slice_ import Slice

slc = Slice()
mask = slc.create_new_mask() # matriz do numpy
first_pixel = mask.matrix[0, 0, 0]

PubSub events

# test.py
from pubsub import pub as Publisher
def a(value):
    print(f"function a received {value}")

def b(value):
    print(f"function b received {value}")

Publisher.subscribe(a, "test_pubsub")
Publisher.subscribe(b, "test_pubsub")
Publisher.sendMessage("test_pubsub", value=42)
$ python test.py
function a received 42
function b received 42

Some PubSub events in InVesalius

"Update slice viewer"
"Update slice viewer {orientation}"
"Reload actual slice"
"Reload actual slice {orientation}"
("Set scroll position", orientation)
"Render volume viewer"
"Load surface actor into viewer"
"Remove surface actor from viewer"
"Load volume into viewer"
"Unload volume"
"Close project data"
"Enable style"
"Disable actual style"

Styles

  • Used for adding and working with events in slices and 3D
# my_style.py
from invesalius.data import styles

# `Base3DInteractorStyle` ou `DefaultInteractorStyle`
class MyStyle(styles.BaseImageInteractorStyle):
    def __init__(self, viewer):
        super().__init__(viewer)
        self.AddObserver("LeftButtonPressEvent", self.OnMouseLeftPress)
    def OnMouseLeftPress(self, obj, evt):
        print(obj, evt)

Styles

# main.py
import wx
from invesalius.data import styles
from pubsub import pub as Publisher
from . import my_style

def load():
    # Addd `Style` to `StyleManager` on InVesalius:
    style_id = styles.Styles.add_style(my_style.MyStyle, 2)
    # Enable the `style` created:
    Publisher.sendMessage("Enable style", style=style_id)

When modifying a mask

#  The first position in each orientation indicates 
# whether the threshold has already been applied.
# If changing, mark these positions with 1
mask.matrix[0] = 1
mask.matrix[:, 0, :] = 1
mask.matrix[:, :, 0] = 1
mask.matrix[1:, 1:, 1:] = 42
# Mark mask edited
mask.was_edited = True
# Updates the mask 3D preview
mask.modified()
# clean history (Ctrl-Z Ctrl-Y)
mask.clear_history()
# clean caches
self.viewer.discard_mask_cache(all_orientations=True, vtk_cache=True)
# Reload the current mask
Publisher.sendMessage('Reload actual slice')

Plugin examples

https://github.com/tfmoraes/inv3_plugins_examples